/*********************************************************************/
char    *pname="smjoin";
char    *usage="ff3e::1234:4321 [-s 2001:250:ccce::] [-p 1700] [-raw] [-c 10] [-k 1000] [-pid file]";

/*
 * Copyright (C) 2008 CERNET Network Center 
 * smclock/smjoin/smclock6/smjoin6/smsend6/smrec6 
 *
 * Design and coding: 
 * Xing Li <xing@cernet.edu.cn> 
 * Congxiao Bao <congxiao@cernet.edu.cn>
 *
 * Some portions of the code are copied and modified from 
 * mcastClient.c fpont 12/99 pont.net 
 * ssmping package * Copyright (C) 2005  Stig Venaas 
 * 
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 */
/***************************************/
/*
 * Copyright (C) 2005  Stig Venaas <venaas@uninett.no>
 * $Id: ssmping.c,v 1.22 2005/11/29 16:27:26 sv Exp $
 *
 * Contributions:
 * Solaris support by Alexander Gall <gall@switch.ch>
 * Initial Windows support by Nick Lamb <njl@ecs.soton.ac.uk>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 */
/***************************************/

/**************************************************************************/
/* Header files needed for this sample program                            */
/**************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>

/**************************************************************************/
/* Constants used by this program                                         */
/**************************************************************************/
#define SERVER_PORT     1501
#define BUFFER_LENGTH    100
#define FALSE              0


#ifndef MCAST_JOIN_SOURCE_GROUP
#define MCAST_JOIN_SOURCE_GROUP         46
struct group_source_req {
  uint32_t                gsr_interface; /* interface index */
  struct sockaddr_storage gsr_group;     /* group address */
  struct sockaddr_storage gsr_source;    /* source address */
};
#endif


void errx(char *, ...);
void err(char *, ...);
char *addr2string();
void joinchannel();
void joingroup();

int     main (argc,argv)
	int     argc;
	char    **argv;
{
        int     ki=0, kfi=0, kfin[10];
   /***********************************************************************/
   /* Variable and structure definitions.                                 */
   /***********************************************************************/
   int    sd=-1, rc;
   char   buffer[BUFFER_LENGTH];
   struct sockaddr_in6 serveraddr;
   struct sockaddr_in6 clientaddr;
   int    clientaddrlen = sizeof(clientaddr);
   char   grpstring[1000], srcstring[1000], clistring[1000];
   int    j=0;
   uint32_t intface=0;
   struct hostent *h;
   struct sockaddr_in6 cliAddr, servAddr;

	char agroup[1000], asource[1000];
    	int  	iport, cliport;
    	int	num=10;
    	int	raw=0;
	int	kknum=1;
    	int	asm2=1;
    	int 	ssm2=0;
    	int	sss=0;
    	int	mbyte;

	int 	ipid = 0;
	char	pid[1000];

    strcpy(agroup, "ff3e::1234:4321");
    strcpy(asource, "2001:250:ccce::");
    iport = 1501;


    for (ki = 1; ki < argc; ki++) {
       if ('-' != argv[ki][0])         		kfin[++kfi] = ki;
       else if (strcmp("-s", argv[ki]) == 0)   	{sss=1; strcpy(asource, argv[++ki]);}
       else if (strcmp("-p", argv[ki]) == 0)   	iport = atoi(argv[++ki]);

       else if (strcmp("-k", argv[ki]) == 0)   	kknum = atoi(argv[++ki]);
       else if (strcmp("-c", argv[ki]) == 0)   	num = atoi(argv[++ki]);
       else if (strcmp("-raw", argv[ki]) == 0) 	raw = 1;
       else if (strcmp("-pid", argv[ki]) == 0)  {
	 	ipid = 1;
		strcpy(pid, argv[++ki]);
	}
       else {
       	fprintf(stderr, "%s: illegal option %s \n", pname, argv[ki]);
       	fprintf(stderr, "Usage: %s %s \n", pname, usage);
       	exit(1);
       }
    }

    if (kfi == 1) {
            strcpy(agroup, argv[ kfin[1] ]); 
	    if (sss == 1) {asm2 = 0; ssm2 = 1;}
	    else          {asm2 = 1; ssm2 = 0;}
    }
    else {
	    if (sss == 1) {asm2 = 0; ssm2 = 0;}
	    else ;
    }

	/* printf("asm2= %d ssm2= %d\n", asm2, ssm2); */
	/* printf ("ipid= %d pid= %s\n", ipid, pid); */
	if (ipid == 1) {
		if (write_pid(pid) < 0)	{
			fprintf(stderr, "Cannot write %s\n", pid);
			exit(-1);
		}
	}



   /***********************************************************************/
   /* A do/while(FALSE) loop is used to make error cleanup easier.  The   */
   /* close() of each of the socket descriptors is only done once at the  */
   /* very end of the program.                                            */
   /***********************************************************************/
   do
   {
      /********************************************************************/
      /* The socket() function returns a socket descriptor representing   */
      /* an endpoint.  The statement also identifies that the INET        */
      /* (Internet Protocol) address family with the UDP transport        */
      /* (SOCK_DGRAM) will be used for this socket.                       */
      /********************************************************************/
      sd = socket(AF_INET6, SOCK_DGRAM, 0);
      if (sd < 0)
      {
         perror("socket() failed");
         break;
      } 

      /********************************************************************/
      /* After the socket descriptor is created, a bind() function gets a */
      /* unique name for the socket.  In this example, the user sets the  */
      /* s_addr to zero, which means that the UDP port of 3555 will be    */
      /* bound to all IP addresses on the system.                         */
      /********************************************************************/
      memset(&serveraddr, 0, sizeof(serveraddr));
      serveraddr.sin6_family      = AF_INET6;
      serveraddr.sin6_port        = htons(iport);
      serveraddr.sin6_addr   = in6addr_any;

      rc = bind(sd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
      if (rc < 0)
      {
         perror("bind() failed");
         break;
      } 

/*****************************************/

if (ssm2 == 1) {
 h = gethostbyname2(agroup, AF_INET6);
  if(h==NULL) {
    printf("unknown host\n");
    exit(1);
  }
        memcpy((char *)&servAddr.sin6_addr, h->h_addr, h->h_length);
        servAddr.sin6_family = h->h_addrtype;
	servAddr.sin6_port = htons(1600);
        strcpy(grpstring,  addr2string((struct sockaddr *)&servAddr, (socklen_t)sizeof(servAddr)));
        /* printf("grpstring= %s\n", grpstring); */

 h = gethostbyname2(asource, AF_INET6);
  if(h==NULL) {
    printf("unknown host\n");
    exit(1);
  }
        memcpy((char *)&cliAddr.sin6_addr, h->h_addr, h->h_length);
        cliAddr.sin6_family = h->h_addrtype;
	cliAddr.sin6_port = htons(1600);
        strcpy(srcstring,  addr2string((struct sockaddr *)&cliAddr, (socklen_t)sizeof(cliAddr)));
        /* printf("srcstring= %s\n", srcstring); */
	joinchannel(sd, (struct sockaddr *)&cliAddr,
                (struct sockaddr *)&servAddr, intface, (struct sockaddr *)&servAddr); 
}

if (asm2 == 1) {
 h = gethostbyname2(agroup, AF_INET6);
  if(h==NULL) {
    printf("unknown host\n");
    exit(1);
  }
        memcpy((char *)&servAddr.sin6_addr, h->h_addr, h->h_length);
        servAddr.sin6_family = h->h_addrtype;
	servAddr.sin6_port = htons(1600);
        strcpy(grpstring,  addr2string((struct sockaddr *)&servAddr, (socklen_t)sizeof(servAddr)));
        /* printf("grpstring= %s\n", grpstring); */
	joingroup  (sd, (struct sockaddr *)&servAddr, intface, (struct sockaddr *)&servAddr); 
}


/*****************************************/

      /********************************************************************/
      /* The server uses the recvfrom() function to receive that data.    */
      /* The recvfrom() function waits indefinitely for data to arrive.   */
      /********************************************************************/
while(1) {
      rc = recvfrom(sd, buffer, sizeof(buffer), 0,
                    (struct sockaddr *)&clientaddr,
                    &clientaddrlen);
      if (rc < 0)
      {
         perror("recvfrom() failed");
         break;
      }
	 mbyte = strlen(buffer);
	 strcpy(clistring,  addr2string((struct sockaddr *)&clientaddr, sizeof(clientaddr)));
         cliport = ntohs(clientaddr.sin6_port);

/* printf("j=%d kknum=%d\n", j, kknum); */
if ((j%kknum) == 0) {

      	if (ssm2 ==1)		printf("SSM: [%s]#%d [%s]#%d %d bytes ", 
				clistring, cliport, grpstring, iport, mbyte);
      	else if (asm2 ==1)	printf("ASM: [%s]#%d [%s]#%d %d bytes ", 
				clistring, cliport, grpstring, iport, mbyte);
      	else			printf("UDP: [%s]#%d [::1]#%d %d bytes ", clistring, cliport, iport, mbyte);

	if (raw == 1)	printf("{%s} ", buffer);
	else		printf("MSG ");

	printf("(%d)\n", j);
}

	j++;
	if (j == num)	return(0);

}

      /********************************************************************/
      /* Program complete                                                 */
      /********************************************************************/

   } while (FALSE);

   /***********************************************************************/
   /* Close down any open socket descriptors                              */
   /***********************************************************************/
   if (sd != -1)
      close(sd);
return(0);
}


/***************************************************************************/
char *addr2string(struct sockaddr *addr, socklen_t len) {
    static char addr_buf[2][INET6_ADDRSTRLEN];
    static int i = 0;
    i = !i;
    if (getnameinfo(addr, len, addr_buf[i], sizeof(addr_buf[i]),
            NULL, 0, NI_NUMERICHOST)) {
            err("getnameinfo");
        return NULL;
    }
    return addr_buf[i];
}

void joinchannel(int s, struct sockaddr *src, struct sockaddr *grp, uint32_t intface, struct sockaddr *ifaddr) {
    int level;
    socklen_t addrlen;
#ifdef WIN32
    struct ip_mreq_source imsr;
#endif
    struct group_source_req gsreq;
    
    if (src->sa_family != grp->sa_family) {
        fprintf(stderr, "joinchannel failed, source and group must be of same address family\n");
        exit(1);
    }

    if (ifaddr && ifaddr->sa_family != grp->sa_family) {
        fprintf(stderr, "joinchannel failed, group and interface must be of same address family\n");
        exit(1);
    }
    
    switch (grp->sa_family) {
    case AF_INET:
        addrlen = sizeof(struct sockaddr_in);
        level = IPPROTO_IP;
        break;
    case AF_INET6:
        addrlen = sizeof(struct sockaddr_in6);
        level = IPPROTO_IPV6;
        break;
    default:
        fprintf(stderr, "joinchannel failed, unsupported address family\n");
        exit(1);
    }

    memset(&gsreq, 0, sizeof(gsreq));
    memcpy(&gsreq.gsr_source, src, addrlen);
    memcpy(&gsreq.gsr_group, grp, addrlen);
    gsreq.gsr_interface = intface;
#ifndef WIN32
    if (setsockopt(s, level, MCAST_JOIN_SOURCE_GROUP, (char *)&gsreq, sizeof(gsreq)) < 0)
        errx("Failed to join multicast channel");
#else
    if (setsockopt(s, level, MCAST_JOIN_SOURCE_GROUP, (char *)&gsreq, sizeof(gsreq)) >= 0)
        return;
    if (level != IPPROTO_IP)
        errx("Failed to join multicast channel");

    /* For Windows XP the above setsockopt fails, below works for IPv4
     * While for Windows Vista the above should work
     */
    
    memset(&imsr, 0, sizeof(imsr));
    imsr.imr_sourceaddr = ((struct sockaddr_in *)src)->sin_addr;
    imsr.imr_multiaddr = ((struct sockaddr_in *)grp)->sin_addr;
    imsr.imr_interface = ((struct sockaddr_in *)ifaddr)->sin_addr;

    if (setsockopt(s, level, IP_ADD_SOURCE_MEMBERSHIP, (char *)&imsr, sizeof(imsr)) < 0)
        errx("Failed to join multicast channel");
#endif
}

void joingroup(int s, struct sockaddr *grp, uint32_t intface, struct sockaddr *ifaddr) {
    int e;
#ifdef MCAST_JOIN_GROUP
    int level;
    socklen_t addrlen;
    struct group_req greq;
#endif

    if (grp && ifaddr && ifaddr->sa_family != grp->sa_family) {
        fprintf(stderr, "joingroup failed, group and interface must be of same address family\n");
        exit(1);
    }
    
    switch (grp->sa_family) {
    case AF_INET:
#ifdef MCAST_JOIN_GROUP
        addrlen = sizeof(struct sockaddr_in);
        level = IPPROTO_IP;
#else
        {
            struct ip_mreq mreq;
            memset(&mreq, 0, sizeof(mreq));
            mreq.imr_multiaddr = ((struct sockaddr_in *)grp)->sin_addr;
            mreq.imr_interface = ((struct sockaddr_in *)ifaddr)->sin_addr;
            e = setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&mreq, sizeof(mreq));
        }
#endif
        break;
    case AF_INET6:
#ifdef MCAST_JOIN_GROUP
        addrlen = sizeof(struct sockaddr_in6);
        level = IPPROTO_IPV6;
#else
        {
            struct ipv6_mreq mreq6;
            memset(&mreq6, 0, sizeof(mreq6));
            mreq6.ipv6mr_multiaddr = ((struct sockaddr_in6 *)grp)->sin6_addr;
            mreq6.ipv6mr_interface = intface;
            e = setsockopt(s, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char *)&mreq6, sizeof(mreq6));
        }
#endif
        break;
    default:
        fprintf(stderr, "joingroup failed, unsupported address family\n");
        exit(1);
    }

#ifdef MCAST_JOIN_GROUP
    memset(&greq, 0, sizeof(greq));
    memcpy(&greq.gr_group, grp, addrlen);
    greq.gr_interface = intface;
    e = setsockopt(s, level, MCAST_JOIN_GROUP, (char *)&greq, sizeof(greq));
#endif

    if (e < 0)
        errx("Failed to join multicast group");
}

int write_pid(pname)
char    pname[];
{
int     pid;
FILE    *fi;

        if ((fi = fopen(pname, "w")) == NULL) {
                return -1;
        }
        pid = getpid();
        fprintf(fi, "pid= %d\n", pid);
        fclose(fi);
        return 1;
}

